﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Web;
using System.Web.Mvc;

namespace Web.Infrastructure.JSExtensions
{
    public static class GridHtmlHelper
    {
        public static GridRenderer<TItem> GridFor<TModel, TItem>(
            this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, IEnumerable<TItem>>> collection)
        {
            if (collection == null)
                throw new ArgumentNullException("collection");

            string collectionName = ExpressionHelper.GetExpressionText(collection);
            return new GridRenderer<TItem>(collectionName, htmlHelper);
        }

        public class GridRenderer<TItem>
        {
            #region Constants and Fields

            private readonly string _collectionName;

            private readonly List<ColumnInfo> _columnsInfoes;

            private readonly HtmlHelper _htmlHelper;

            private readonly UrlHelper _urlHelper;

            private readonly ViewDataDictionary<TItem> _viewDataDictionary;

            private string _editEntityLink;

            private string _identityPropertyName;

            private string _noItemsText;

            private string _paginatorLink;

            private string _tableCssClass;

            private readonly Dictionary<string, string> _rowAttributes = new Dictionary<string, string>();

            #endregion

            public GridRenderer(string collectionName, HtmlHelper htmlHelper)
            {
                if (string.IsNullOrWhiteSpace(collectionName))
                    throw new ArgumentNullException("collectionName");
                if (htmlHelper == null)
                    throw new ArgumentNullException("htmlHelper");

                _collectionName = collectionName;
                _htmlHelper = htmlHelper;
                _columnsInfoes = new List<ColumnInfo>();
                _viewDataDictionary = new ViewDataDictionary<TItem>();
                _urlHelper = new UrlHelper(_htmlHelper.ViewContext.RequestContext);
            }

            private bool CheckboxIsEnabled
            {
                get
                {
                    return !string.IsNullOrEmpty(_identityPropertyName);
                }
            }

            private int ColumnsCount
            {
                get
                {
                    return CheckboxIsEnabled ? _columnsInfoes.Count + 1 : _columnsInfoes.Count;
                }
            }

            private bool EntityEditUrlExists
            {
                get
                {
                    return !string.IsNullOrEmpty(_editEntityLink)
                           && !string.IsNullOrEmpty(_identityPropertyName);
                }
            }

            private bool PaginatorIsEnabled
            {
                get
                {
                    return !string.IsNullOrEmpty(_paginatorLink);
                }
            }

            /// <summary>
            /// Specified column.
            /// </summary>
            /// <typeparam name="TProperty">
            /// The type of the property.
            /// </typeparam>
            /// <param name="column">
            /// Property to be represented as column.
            /// </param>
            /// <param name="template">
            /// Custom template.
            /// </param>
            /// <param name="cssClass">
            /// The CSS class to be applied to header and cells of the column.
            /// </param>
            public GridRenderer<TItem> Column<TProperty>(
                Expression<Func<TItem, TProperty>> column, string template = null, string cssClass = null)
            {
                if (column == null)
                    throw new ArgumentNullException("column");

                ModelMetadata metaData = ModelMetadata.FromLambdaExpression(column, _viewDataDictionary);
                var columnInfo = new ColumnInfo
                    {
                        PropertyName = metaData.PropertyName,
                        Title = metaData.GetDisplayName(),
                        Template = template,
                        CssClass = cssClass
                    };
                _columnsInfoes.Add(columnInfo);
                return this;
            }

            /// <summary>
            /// Specifies the edit entity action. If it set with identity property, then to each row link for editing entity will be added.
            /// </summary>
            /// <param name="editEntityAction">
            /// The edit entity action.
            /// </param>
            public GridRenderer<TItem> EditEntityAction(ActionResult editEntityAction)
            {
                if (editEntityAction == null)
                    throw new ArgumentNullException("editEntityAction");

                _editEntityLink = _urlHelper.Action(editEntityAction);
                return this;
            }

            /// <summary>
            /// Specifies the identity of entity. If it is set a checkbox column will be rendered.
            /// </summary>
            /// <typeparam name="TProperty">
            /// The type of the property.
            /// </typeparam>
            /// <param name="identity">
            /// The identity.
            /// </param>
            public GridRenderer<TItem> Identity<TProperty>(Expression<Func<TItem, TProperty>> identity)
            {
                if (identity == null)
                    throw new ArgumentNullException("identity");

                _identityPropertyName = ExpressionHelper.GetExpressionText(identity);
                return this;
            }

            /// <summary>
            /// Attributes the specified attribute name.
            /// </summary>
            /// <typeparam name="TProperty">The type of the property.</typeparam>
            /// <param name="attributeName">Name of the attribute.</param>
            /// <param name="attributeValue">The attribute value.</param>
            /// <exception cref="System.ArgumentNullException"></exception>
            public GridRenderer<TItem> Attribute<TProperty>(string attributeName, Expression<Func<TItem, TProperty>> attributeValue)
            {
                if (attributeName == null)
                    throw new ArgumentNullException("attributeName");

                if (attributeValue == null)
                    throw new ArgumentNullException("attributeValue");

                var value = ExpressionHelper.GetExpressionText(attributeValue);
                if (_rowAttributes.ContainsKey(value))
                {
                    _rowAttributes[attributeName] = value;
                }
                else
                {
                    _rowAttributes.Add(attributeName, value);
                }

                return this;
            }

            /// <summary>
            /// Specifies the text, which would be rendered if there are no items to be displayed.
            /// </summary>
            /// <param name="emptyGridText">
            /// The empty grid text.
            /// </param>
            public GridRenderer<TItem> NoItemsText(string emptyGridText)
            {
                _noItemsText = emptyGridText;
                return this;
            }

            /// <summary>
            /// Specifies the listing action for paginator. If it is set, paginator would be rendered in the top of the table.
            /// </summary>
            /// <param name="paginatorAction">
            /// The paginator action.
            /// </param>
            public GridRenderer<TItem> PaginatorAction(ActionResult paginatorAction)
            {
                if (paginatorAction == null)
                    throw new ArgumentNullException("paginatorAction");

                _paginatorLink = _urlHelper.Action(paginatorAction);
                return this;
            }

            /// <summary>
            /// Renders the whole grid.
            /// </summary>
            public MvcHtmlString Render()
            {
                TagBuilder tableTag = GetTableTag();
                return MvcHtmlString.Create(HttpUtility.HtmlDecode(tableTag.ToString()));
            }

            /// <summary>
            /// Adds the specified CSS class to rendered table.
            /// </summary>
            /// <param name="cssClass">
            /// The CSS class.
            /// </param>
            public GridRenderer<TItem> TableCssClass(string cssClass)
            {
                _tableCssClass = cssClass;
                return this;
            }

            /// <summary>
            /// Returns a <see cref="System.String" /> that represents this instance.
            /// </summary>
            /// <returns>
            /// A <see cref="System.String" /> that represents this instance.
            /// </returns>
            public override string ToString()
            {
                return Render().ToString();
            }

            private static TagBuilder GetTableBodyRowCellTag(ColumnInfo columnInfo)
            {
                var tableBodyRowCellTag = new TagBuilder("td");
                tableBodyRowCellTag.MergeAttribute("class", columnInfo.CssClass);

                string cellTemplate = columnInfo.HasCustomCellTemplate
                                          ? columnInfo.Template
                                          : string.Format("<%= item.{0} %>", columnInfo.PropertyName);

                tableBodyRowCellTag.InnerHtml = cellTemplate;
                return tableBodyRowCellTag;
            }

            private static TagBuilder GetTableHeaderCellSortLink(ColumnInfo columnInfo)
            {
                var tableHeaderCellLink = new TagBuilder("a");
                tableHeaderCellLink.MergeAttributes(
                    new Dictionary<string, string>
                        {
                            { "column", columnInfo.PropertyName },
                            { "class", "no-link" },
                            { "direction", "<%=Direction==0?'1':'0'%>" },
                            { "href", "#" }
                        });
                tableHeaderCellLink.SetInnerText(columnInfo.Title);
                return tableHeaderCellLink;
            }

            private static MvcHtmlString SortColumn(ColumnInfo columnInfo)
            {
                const string template =
                    "<th <%=((Direction==0 && Column==\"{0}\")?'class=\"sort_asc {1}\"':'class=\"sort_desc {1}\"')%> >{2}</th>";

                TagBuilder tableHeaderCellLink = GetTableHeaderCellSortLink(columnInfo);

                string result = string.Format(
                    template, columnInfo.PropertyName, columnInfo.CssClass, tableHeaderCellLink);

                return MvcHtmlString.Create(HttpUtility.HtmlDecode(result));
            }

            private TagBuilder GetTableBodyCheckboxCell()
            {
                var tableBodyCheckboxCell = new TagBuilder("td");
                tableBodyCheckboxCell.AddCssClass("chk");
                var tableBodyCheckboxCellCheckbox = new TagBuilder("input");
                tableBodyCheckboxCellCheckbox.MergeAttributes(
                    new Dictionary<string, string>
                        {
                            { "type", "checkbox" },
                            { "value", string.Format("<%= item.{0} %>", _identityPropertyName) },
                            { "name", "items" }
                        });

                tableBodyCheckboxCell.InnerHtml = tableBodyCheckboxCellCheckbox.ToString();
                return tableBodyCheckboxCell;
            }

            private string GetTableBodyRowInnerHtml()
            {
                var tableBodyRowInnerHtmlBuilder = new StringBuilder();
                if (CheckboxIsEnabled)
                {
                    TagBuilder tableBodyCheckboxCell = GetTableBodyCheckboxCell();
                    tableBodyRowInnerHtmlBuilder.Append(tableBodyCheckboxCell);
                }
                foreach (TagBuilder tableBodyRowCellTag in _columnsInfoes.Select(GetTableBodyRowCellTag))
                    tableBodyRowInnerHtmlBuilder.Append(tableBodyRowCellTag);

                return tableBodyRowInnerHtmlBuilder.ToString();
            }

            private TagBuilder GetTableBodyRowTag()
            {
                var tableBodyRowTag = new TagBuilder("tr");
                if (EntityEditUrlExists)
                {
                    tableBodyRowTag.MergeAttribute(
                        "href",
                        string.Format("{0}?{1}=<%=item.{1}%>", _editEntityLink, _identityPropertyName));
                }
                tableBodyRowTag.AddCssClass("gridrow");
                foreach (var rowAttribute in _rowAttributes)
                {
                    tableBodyRowTag.Attributes.Add(rowAttribute.Key, "<%= item." + rowAttribute.Value + " %>");
                }

                string tableBodyRowInnerHtml = GetTableBodyRowInnerHtml();
                tableBodyRowTag.InnerHtml = tableBodyRowInnerHtml;
                return tableBodyRowTag;
            }

            private TagBuilder GetTableBodyTag()
            {
                var tableBodyTag = new TagBuilder("tbody");
                TagBuilder tableBodyRowTag = GetTableBodyRowTag();
                TagBuilder tableEmptyBodyRowTag = GetTableEmptyBodyRowTag();

                tableBodyTag.InnerHtml +=
                    string.Format(
                        "<% if ({0}.length>0) {{ %><% _.each({0}, function(item) {{ %>", _collectionName);
                tableBodyTag.InnerHtml += tableBodyRowTag.ToString();
                tableBodyTag.InnerHtml += "<% });%><% } else { %>";
                tableBodyTag.InnerHtml += tableEmptyBodyRowTag.ToString();
                tableBodyTag.InnerHtml += "<% } %>";

                return tableBodyTag;
            }

            private TagBuilder GetTableEmptyBodyRowTag()
            {
                var tableEmptyBodyRowTag = new TagBuilder("tr");
                var tableEmptyBodyCellTag = new TagBuilder("td");
                tableEmptyBodyCellTag.MergeAttribute(
                    "colspan", ColumnsCount.ToString(CultureInfo.InvariantCulture));
                tableEmptyBodyCellTag.InnerHtml = _noItemsText;
                tableEmptyBodyRowTag.InnerHtml = tableEmptyBodyCellTag.ToString();
                return tableEmptyBodyRowTag;
            }

            private TagBuilder GetTableFooterTag()
            {
                var tableFooterTag = new TagBuilder("tfoot");
                MvcHtmlString paginator = _htmlHelper.Paginator(ColumnsCount, _paginatorLink);
                tableFooterTag.InnerHtml = paginator.ToString();
                return tableFooterTag;
            }

            private TagBuilder GetTableHeaderTag()
            {
                var tableHeaderTag = new TagBuilder("thead");
                var tableHeaderRowTag = new TagBuilder("tr");

                if (CheckboxIsEnabled)
                {
                    var tableHeaderCheckbox = new TagBuilder("th");
                    tableHeaderCheckbox.AddCssClass("chk");
                    tableHeaderRowTag.InnerHtml = tableHeaderCheckbox.ToString();
                }

                foreach (ColumnInfo columnInfo in _columnsInfoes)
                {
                    MvcHtmlString sortColumn = SortColumn(columnInfo);
                    tableHeaderRowTag.InnerHtml += sortColumn.ToString();
                }
                tableHeaderTag.InnerHtml = tableHeaderRowTag.ToString();
                return tableHeaderTag;
            }

            private TagBuilder GetTableTag()
            {
                var tableTag = new TagBuilder("table");
                tableTag.AddCssClass("grid");
                tableTag.AddCssClass(_tableCssClass);

                TagBuilder tableHeaderTag = GetTableHeaderTag();
                TagBuilder tableBodyTag = GetTableBodyTag();

                tableTag.InnerHtml += string.Concat(tableHeaderTag, tableBodyTag);

                if (PaginatorIsEnabled)
                {
                    TagBuilder tableFooterTag = GetTableFooterTag();
                    tableTag.InnerHtml += tableFooterTag;
                }

                return tableTag;
            }

            private class ColumnInfo
            {
                public string CssClass { get; set; }

                public bool HasCustomCellTemplate
                {
                    get
                    {
                        return Template != null;
                    }
                }

                public string PropertyName { get; set; }

                public string Template { get; set; }

                public string Title { get; set; }
            }
        }
    }
}